import applyMixin from './mixin'
import devtoolPlugin from './plugins/devtool'
import ModuleCollection from './module/module-collection'
import {forEachValue, isObject, isPromise, assert} from './util'

let Vue // bind on install

// vuex文档说Vuex的状态存储是响应式的，在源码的何处做的响应式？
// 答：在resetStoreVM方法中通过new Vue()，并且将state作为data的方式来创建数据响应系统。
// 在actions中是否可获得getters
// 答：可以，详情可查看makeLocalContext方法
export class Store {
  constructor(options = {}) {
    // Auto install if it is not done yet and `window` has `Vue`.
    // To allow users to avoid auto-installation in some cases,
    // this code should be placed here. See #731
    // 如果未将Vuex安装到Vue中，并且Vue存在于window对象上，则安装
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }

    if (process.env.NODE_ENV !== 'production') {
      // 必须在创建store实例之前使用Vue.use()方法注册Vuex
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `Store must be called with the new operator.`)
    }

    const {
      plugins = [], // 插件，Vuex插件就是一个函数
      strict = false // 是否为严格模式。在严格模式下，无论何时发生了状态变更且不是由 mutation 函数引起的，将会抛出错误
    } = options

    // store internal state
    // 控制状态state被修改的方式
    // 保证可以在Vuex期望的位置修改，比如在mutations中修改，
    // 如果在Vuex不期望的位置，则会给出警告
    this._committing = false
    // Vuex的处理方法是将传入Store的options中的模块树全部解析到仓库store对象中，
    // 每个模块中的state、actions、mutations和getters全部放置在store对象中，
    // 比如此处的_actions属性就用于放置任何层级的所有模块的actions，而_actions中的键是根据对模块
    // 的命名空间的解析得出的，实例：
    /**
     * let storeObj = {
     *    modules:{
     *      a:{
     *        actions:{
     *          hello(){}
     *        }
     *      },
     *      b:{
     *        namespaced:true,
     *        actions:{
     *          go(){}
     *        }
     *      }
     *    }
     * }
     * 如上所示，对于a模块的hello来说，解析的action的键的结果是：'hello'
     * 而对于b模块的go来说，解析的action的键的结果是：'b/go'，
     * 所以说，在分发action时，对于a模块的hello来说，应该使用dispatch('hello')，
     * 而对于b模块的go来说，应该使用dispatch('b/go')的方式
     */
    this._actions = Object.create(null)
    this._actionSubscribers = []
    // 可参考上方对_actions的解释
    this._mutations = Object.create(null)
    // 可参考上方对_actions的解释
    this._wrappedGetters = Object.create(null)
    // ModuleCollection模块集合对象
    this._modules = new ModuleCollection(options)
    // vuex中的有Module的概念，也就是模块。每个模块都可以设置一个namespaced属性，表示是否
    // 将当前模块生成一个带有命名空间的模块。如果namespaced为true，则会为当前模块生成一个自root模块至当前模块的
    // namespace（也就是说，只有namespaced属性为true的模块才会添加到_modulesNamespaceMap属性中），
    // 而_modulesNamespaceMap属性是一个映射对象，其中键为之前提到生成的namespace，而值为
    // 对应的模块对象
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()

    // bind commit and dispatch to self
    const store = this
    const {dispatch, commit} = this

    // 为dispatch和commit绑定执行上下文是当前store对象
    // dispatch方法用于分发action
    this.dispatch = function boundDispatch(type, payload) {
      return dispatch.call(store, type, payload)
    }
    // commit方法用于分发mutation
    this.commit = function boundCommit(type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    this.strict = strict

    // 根模块的状态对象
    // 整个仓库的状态都会被添加到根模块的state对象下，
    // 注意状态的添加与namespaced无关，命名空间只会约束对actions、mutations和getters的方法
    // 实例：
    /**
     * let storeObj = {
     *    state:{
     *      a:'a'
     *    }
     *    modules:{
     *      b:{
     *        state:{
     *          b:'b'
     *        }
     *      },
     *      c:{
     *        namespaced:true
     *        state:{
     *          c:'c'
     *        }
     *      }
     *    }
     * }
     * 如上所示：生成的根模块的state的最终结果是：
     * root.state = {
     *  a:'a',
     *  b:{
     *    b:'b'
     *  },
     *  c:{
     *    c:'c'
     *  }
     * }
     */
    const state = this._modules.root.state

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state)

    // apply plugins
    plugins.forEach(plugin => plugin(this))

    if (Vue.config.devtools) {
      devtoolPlugin(this)
    }
  }

  get state() {
    return this._vm._data.$$state
  }

  set state(v) {
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `Use store.replaceState() to explicit replace store state.`)
    }
  }

  commit(_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = {type, payload}
    const entry = this._mutations[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    this._withCommit(() => {
      entry.forEach(function commitIterator(handler) {
        handler(payload)
      })
    })
    this._subscribers.forEach(sub => sub(mutation, this.state))

    if (
      process.env.NODE_ENV !== 'production' &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }

  dispatch(_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = {type, payload}
    // 这里entry中的actions已经通过installModule方法进行了包装，而不是原始的action函数
    const entry = this._actions[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    this._actionSubscribers.forEach(sub => sub(action, this.state))

    return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  }

  subscribe(fn) {
    return genericSubscribe(fn, this._subscribers)
  }

  subscribeAction(fn) {
    return genericSubscribe(fn, this._actionSubscribers)
  }

  // 监听state和getters的变化，getter相当于computed，而cb就是getter监听的数据改变后的回调函数，
  // 其中cb的参数为newVal和oldVal
  watch(getter, cb, options) {
    if (process.env.NODE_ENV !== 'production') {
      assert(typeof getter === 'function', `store.watch only accepts a function.`)
    }

    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  }

  replaceState(state) {
    this._withCommit(() => {
      this._vm._data.$$state = state
    })
  }

  registerModule(path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]

    if (process.env.NODE_ENV !== 'production') {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      assert(path.length > 0, 'cannot register the root module by using registerModule.')
    }

    this._modules.register(path, rawModule)
    installModule(this, this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    resetStoreVM(this, this.state)
  }

  unregisterModule(path) {
    if (typeof path === 'string') path = [path]

    if (process.env.NODE_ENV !== 'production') {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
    }

    this._modules.unregister(path)
    this._withCommit(() => {
      const parentState = getNestedState(this.state, path.slice(0, -1))
      Vue.delete(parentState, path[path.length - 1])
    })
    resetStore(this)
  }

  hotUpdate(newOptions) {
    this._modules.update(newOptions)
    resetStore(this, true)
  }

  /**
   * 保证修改store的状态时不会报错
   * @param fn
   * @private
   */
  _withCommit(fn) {
    // _commiting的作用是什么？
    // 答：默认情况下，在mutations之外直接修改state会报错，将_committing设置为true，
    // 可以使之不报错，详情可查看enableStrictMode方法中的源码与注释
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }
}

function genericSubscribe(fn, subs) {
  if (subs.indexOf(fn) < 0) {
    subs.push(fn)
  }

  // 通过订阅的返回值取消订阅
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}

function resetStore(store, hot) {
  store._actions = Object.create(null)
  store._mutations = Object.create(null)
  store._wrappedGetters = Object.create(null)
  store._modulesNamespaceMap = Object.create(null)
  const state = store.state
  // init all modules
  installModule(store, state, [], store._modules.root, true)
  // reset vm
  resetStoreVM(store, state, hot)
}

function resetStoreVM(store, state, hot) {
  const oldVm = store._vm

  // bind store public getters
  // 1）在store上生成getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    computed[key] = () => fn(store)
    // 通过getters属性暴露注册的getters，如果getter所在的模块存在命名空间，则获取getters时，需要
    // 使用命名空间，比如this.$store.getters['hello/a']，其中hello为命名空间化的模块的名字
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

  // use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins
  // 2）使用一个Vue实例为state添加数据响应系统，并且将getters声明为computed
  const silent = Vue.config.silent
  Vue.config.silent = true
  store._vm = new Vue({
    data: {
      $$state: state // 为state添加数据响应系统
    },
    computed // 将getters声明为computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  if (store.strict) {
    enableStrictMode(store)
  }

  // 销毁旧vue对象
  if (oldVm) {
    if (hot) { // hot与webpack的热重载有关
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}

/**
 *
 * @param store 全局唯一的仓库store对象，也就是通过Store类创建的对象
 * @param rootState 根模块的状态
 * @param path 当前模块的path，注意，是完整的path，与namespaced无关
 * @param module 当前模块，Module对象
 * @param hot
 */
function installModule(store, rootState, path, module, hot) {
  const isRoot = !path.length
  // 当前模块的命名空间，注意，只有namespaced属性为true的模块才会出现在namespace中，
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  if (module.namespaced) {
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  // hot的作用是什么？
  // 答：要搞明白hot的作用，就需要知道下面的if中做了什么，下面if块的作用就是改变store的状态，
  // 所以如果设置hot为true，则在调用installModule时就会保留state。
  // 主要作用：
  // 1）用于动态注册模块时提供的preserveState:true的选项，详情可查看官方文档 --> 核心概念 --> Module --> 模块动态注册 --> preserveState的解释
  // 2）在resetStore方法中重置store，但是不重置store的状态
  if (!isRoot && !hot) {
    // 当前模块的父模块的状态state
    const parentState = getNestedState(rootState, path.slice(0, -1))
    // 当前模块的模块名
    const moduleName = path[path.length - 1]
    // 此处的_withCommit()方法的作用是什么？
    // 答：默认情况下，状态对象只可以使用mutations修改，此处如果直接通过Vue.set方法修改状态会报错，
    // 而_withCommit()方法可以保证在Vue.set的过程中不会报错
    store._withCommit(() => {
      // 父状态parentState会添加数据响应系统吗？
      // 答：是的，但是此时还没有为state添加响应系统，因为state的数据响应系统是在resetStoreVM方法中添加的，
      // 这里通过Vue.set方法处理是为了防止state原本就存在数据响应系统
      // 注意：仓库对象store.root.state就是在此处修改的
      // 注意：状态的添加只与模块有关，与namespaced无关
      // 注意：如果installModule方法是在Store的构造函数中调用的，则此时parentState还未添加数据响应系统。
      Vue.set(parentState, moduleName, module.state)
    })
  }

  const local = module.context = makeLocalContext(store, namespace, path)

  module.forEachMutation((mutation, key) => {
    // key参数为当前模块中的mutations中的键
    // mutation参数为当前模块中的mutations中的值
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) => {
    // action.root在何处添加的？
    // 答：在创建Store对象的选项中，actions中的值可以是函数或者对象，而这里的action.root就是
    // 通过对象形式的action指定的，该action.root的作用是在命名空间的模块中注册全局action,
    // 注意，只有actions可以这么做，mutations不可以这么做。
    const type = action.root ? key : namespace + key
    // handler也是通过对象形式的action指定的，详情可查看官方文档
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

/**
 * make localized dispatch, commit, getters and state
 * if there is no namespace, just use root ones
 * 创建一个上下文对象，该上下文对象在创建mutations、getters和actions时都会被用到
 */
function makeLocalContext(store, namespace, path) {
  // 是否存在命名空间，true表示不存在
  const noNamespace = namespace === ''

  // 注意：此处的dispatch和commit方法只会在actions中用到。
  const local = {
    // 对dispatch方法进行包装的原因就是为了在命名空间的模块中可以方便地向模块内部的actions和mutations分发事件，
    // 而无需添加命名空间，也就是说，这里包装的原因就是为了添加命名空间。
    // 难道在action内部进行分发时可以添加额外的options？而且只有在当前模块是命名空间模块时才可以传递options
    // 答：是的，可以指定root:true选项，详情可查看官方文档
    // 注意：只有在action内部调用dispatch时才可以指定options，在外部调用（比如Vue生命周期中）添加options是无效的
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const {payload, options} = args
      let {type} = args

      // 何时会设置options?options的作用是什么？
      // root表示可以在子模块内部直接触发根模块上的动作，否则就会触发当前模块上的动作
      if (!options || !options.root) {
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }

      return store.dispatch(type, payload)
    },

    // 与上方的dispatch方法基本相同
    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const {payload, options} = args
      let {type} = args

      if (!options || !options.root) {
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          return
        }
      }

      store.commit(type, payload, options)
    }
  }

  // getters and state object must be gotten lazily
  // because they will be changed by vm update
  // 翻译：getters和state对象必须被懒获取，因为它们会被vm的更新所改变。
  Object.defineProperties(local, {
    // 在上下文对象上可以获得getters
    getters: {
      // 注意：如果存在命名空间，则会调用makeLocalGetters方法过滤store.getters，
      // 只保留命名空间模块中的getters，这些getters包括当前模块及其子模块中的getters
      // 实例：
      /**
       * let ob = {
       *    getters:{
       *      a(){
       *        return '';
       *      }
       *    },
       *    modules:{
       *      b:{
       *        getters:{
       *          b(){
       *            return '';
       *          }
       *        }
       *      },
       *      c:{
       *        namespaced: true,
       *        getters:{
       *          c(){
       *            return ''
       *          }
       *        },
       *        modules:{
       *          d: {
       *            namespaced:true,
       *            getters:{
       *              d(){
       *                return '';
       *              }
       *            }
       *          },
       *          e:{
       *            getters:{
       *              e(){
       *                return '';
       *              }
       *            }
       *          }
       *        }
       *      }
       *    }
       * }
       *
       * 如上例，对于b模块中的actions来说，其第一个参数的getters可以获得所有的getters，包括['a', 'b', 'c/c', 'c/d/d', 'c/e']
       * 而对于c模块中的action来说，其第一个参数的getters可以获得命名空间中的getters，包括['c/c', 'c/d/d', 'c/e']
       */
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      // 模块只可以获得并改变自己模块的状态
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}

function makeLocalGetters(store, namespace) {
  const gettersProxy = {}

  const splitPos = namespace.length
  // 这里的getters是什么？注意，是store上的getters
  // 答：是在store._wrappedGetters属性上通过一个Vue实例依次创建computed，
  Object.keys(store.getters).forEach(type => {
    // skip if the target getter is not match this namespace
    // 如果不是当前命名空间下的getter，则跳过
    if (type.slice(0, splitPos) !== namespace) return

    // extract local getter type
    // getter的名字，不包括命名空间
    const localType = type.slice(splitPos)

    // Add a port to the getters proxy.
    // Define as getter property because
    // we do not want to evaluate the getters in this time.
    // 无法在此时获取getters中的数据，因为此时getters还未设置
    // 注意：gettersProxy对象上只存在当前命名空间模块的getter
    Object.defineProperty(gettersProxy, localType, {
      get: () => store.getters[type],
      enumerable: true
    })
  })

  return gettersProxy
}

/**
 * 例子：
 * let storeObj = {
 *  modules:{
 *    a:{
 *      namespaced:true,
 *      mutations:{
 *        hello(){
 *        }
 *      }
 *    },
 *    b:{
 *      mutations:{
 *        go(){
 *        }
 *      }
 *    }
 *  }
 * }
 * @param store 唯一的仓库对象
 * @param type 如果当前模块为命名空间模块，则由命名空间namespace与mutation的键组成的路径，如上例
 *    a模块中的hello的type为'a/hello'，而b模块中的go的type为'go'
 * @param handler mutations对象中键值对的值
 * @param local 当前模块的上下文对象
 */
function registerMutation(store, type, handler, local) {
  // 为什么store._mutations[type]为数组？什么情况下会添加多个mutation？
  // 答：不同模块之间可能存在同名的mutation，此时就需要保存多个mutation，实例：
  /**
   * let storeObj = {
   *    modules:{
   *      a:{
   *        mutations:{
   *          hello(){}
   *        }
   *      },
   *      b:{
   *        mutations:{
   *          hello(){}
   *        }
   *      }
   *    }
   * }
   * 如上所示，此时a和b模块就存在两个同名的mutation。但是，如果设置b模块的namespaced为true，
   * 则就不是同名的mutation，因为对于b模块的hello来说，其名字为'b/hello'
   */
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler(payload) {
    // 执行上下文是仓库对象store
    // 这里说明，mutations中函数的参数只有state和payload
    // 注意，state总是本地模块的状态，与命名空间无关
    handler.call(store, local.state, payload)
  })
}

function registerAction(store, type, handler, local) {
  // 同registerMutation方法中的解释
  const entry = store._actions[type] || (store._actions[type] = [])
  // Todo wrappedActionHandler中的cb回调函数的作用是什么？
  // 猜测：从目前的代码看来，没有用，根本没有用到cb
  entry.push(function wrappedActionHandler(payload, cb) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)

    // action总是会返回Promise
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

function registerGetter(store, type, rawGetter, local) {
  // 注意：getters必须是全局唯一的，不像actions和mutations。
  if (store._wrappedGetters[type]) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] duplicate getter key: ${type}`)
    }
    return
  }
  store._wrappedGetters[type] = function wrappedGetter(store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

function enableStrictMode(store) {
  store._vm.$watch(function () {
    return this._data.$$state // 为仓库store的状态添加监听，一旦状态发生改变，并且不会commit引起的，就会报错
  }, () => {
    if (process.env.NODE_ENV !== 'production') {
      assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
    }
  }, {deep: true, sync: true})
}

function getNestedState(state, path) {
  return path.length
    ? path.reduce((state, key) => state[key], state)
    : state
}

// 分发mutation和action时，commit和dispatch方法的第一个参数可以为字符串或者对象，
// 如果是字符串，则为type。如果是对象，则该对象中必须存在type属性，表示分发的类型。
// 该方法就是用于将对象形式的参数转换为字符串形式，从对象中提取type属性。
function unifyObjectStyle(type, payload, options) {
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }

  if (process.env.NODE_ENV !== 'production') {
    assert(typeof type === 'string', `Expects string as the type, but found ${typeof type}.`)
  }

  return {type, payload, options}
}

export function install(_Vue) {
  // 保证vuex只被安装一次
  if (Vue && _Vue === Vue) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}
